A simple OOP library for Lua. It has inheritance, metamethods (operators), class variables and weak mixin support.
https://github.com/kikito/middleclass
前言
本文逐步的分析了 middleclass 模拟 oop 编程的过程。但是建议读者在看这篇文章之前对 Lua 的元表和元方法有一定的了解。
类定义
新建类定义
创建一个 modulde,创建一个空白类定义对象,类定义对象需记录自己的 name.
1 | ---middleclass--- |
测试:
1 | ---Animal--- |
添加__tostring 函数
目前执行 print (Animal) 的时候,输出的是 table: 00B99B68 (类型:指针地址)
可以让 print 的时候输出更友好的内容,比如 class Animal
解决方案
给新建类统一添加__tostring 函数
print (Animal) 时,会隐式调用 tostring (Animal),接着会尝试调用 getmetatable (Animal).__tostring (Animal)
1 | ---middleclass--- |
测试:
1 | ---Animal--- |
模拟 Class 关键字
目前创建一个类定义对象要调用如下写法 Class.class (classname)
可以改为模拟 oop Class 关键字的风格 Class (classname)
解决方案
给 middleclass 添加__call 函数
把一个 table 当做函数调用时,会去尝试执行该 table 的 metatable 中的__call 函数
1 | ---middleclass--- |
测试:
1 | ---Animal--- |
实例化
导入
A 继承 B 的效果是什么?我们粗略的理解为,A 不经显式声明就可以使用 B 的函数 / 变量。
middleclass 使用了一个粗暴的手段:把父类中的所有属性都拷贝到子类中。middleclass 没有把这种手段称为【继承】,而是借用了另外一个术语【导入(Include)】,这时父类被称为【Mixin】。
1 | ---middleclass--- |
实例化
1 | ---middleclass--- |
DefaultMixin
目前 middleclass 出现了两类函数:工具函数和 class 专用函数
class 专用函数,比如,每个新创建的 class 都应拥有实例化的能力。class 专用函数被打包到一个叫 DefaultMixin 表中,通过 include 方便的复制给每个新建 class
1 | ---middleclass--- |
1 | ---Animal--- |
测试:
1 | ---test--- |
静态 vs 成员
lua 本身没有静态与成员函数之分。
middleclass 则定义的更为细致,更为贴近 oop 惯例。
class | instance | |
---|---|---|
静态函数 | 可调用 | 不可调用 |
成员函数 | 可调用(有什么意义?) | 可调用 |
- class 可调用成员函数的意义?必不可少的功能,不然怎么通过 super 来调用父类的成员函数。为实现上述目的,middleclass 对某 class 上定义的函数进行了分组
- 也就是说,函数的声明都是在 class 级别进行的
- class.static : 只有 class 可以调用的函数
- class.__instanceDict : instance 所调用的函数
static 表
上面章节的 class 专用函数,比如实例化函数,middleclass 将其当做静态函数处理;在别的语言中,实例化功能可能埋藏在语言实现的底层,以语言关键字的形式出现,而非静态函数。是的,关键就在于此:middleclass 用静态函数来模拟 new 这个关键字。
middleclass 用 class.static 来记录 class 中的静态函数,并将 class.static 设为 class 元表中__index 的值中。class 得以调用 class.static 中的函数
1 | ---middleclass--- |
__instanceDict 表
middleclass 把新定义的函数,MyClass.func () 或者 MyClass:func (),都通过__newindex 元函数记录在 class.__instanceDict 表中,然后在该 class 创建实例时,把__instanceDict 设为实例的 metatable 以及元表的__index 的值
- 在 middlecalss 眼中,MyClass.func () 或者 MyClass:func () 都是 middleclass 机制中的成员函数
- 在我们的应用层面,MyClass.func () 像静态函数一样使用,MyClass:func () 像成员函数一样使用
1 | ---middleclass--- |
一些细节
- 可以用 ClassA () 的写法来 new 一个实例
- 实例对象的__tostring 元函数
1 | ---middleclass--- |
1 | ---Animal--- |
测试:
1 | ---test--- |
继承
实现【继承】关键字
和 new 一样,middleclass 用静态函数 subclass 来模拟实现 extends 关键字
1 | ---middleclass--- |
1 | ---Animal--- |
1 | ---Dog--- |
测试:
1 | ---test--- |
继承成员函数
middleclass 的做法是把 super 中的所有成员函数在每个子类中都复制一份
- 创建 subclass 时,会调用_propagateInstanceMethod 把当时 super 中所有成员函数复制到 subclass 中
- super 声明新函数时,会把该新函数在 super 所有子类中的__instanceDict 表中也复制一份
实际应用中大部分都是先完整的声明完 super,再去创建其 subclass。 否则的话,super 声明新函数时,会先通过 subclass 中的 \__declaredMethods 判断 subclass 是否声明过该函数 若没有声明,将这个函数复制到 subclass 中的 \__instanceDict 表中,否则不做处理。
__declaredMethods 表中记录的是通过声明创建的函数,__instanceDict 表中记录了所有的函数:声明得到的,以及从所有父类那里复制得到的
基于上述做法,每个子类中的__instanceDict 都包含了其所有父类的__instanceDict 的集合。即子类拥有父类函数。
如果子类重新定义了一个同名函数,那么将覆盖掉父类函数。
子类可以通过 super 关键字来调用函数的父类版本
1 | ---middleclass--- |
1 | ---Animal--- |
1 | ---Dog--- |
测试:
1 | ---test--- |